iT邦幫忙

2022 iThome 鐵人賽

DAY 19
2

參數驗證

參數驗證 middleware 可以用於 Actions 及事件的參數驗證。

Fastest Validator

Moleculer 預設使用與本框架同一個作者所開發的 fastest-validator 套件[2] 。

範例:快速使用,可以設為 "Fastest"true

moleculer.config.js

module.exports = {
    nodeID: "node-100",
    validator: "Fastest"
}

範例:選項設定,更多選項請參閱[2]

moleculer.config.js

module.exports = {
    nodeID: "node-100",
    validator: {
        type: "Fastest",
        options: {
            useNewCustomCheckerFunction: true,
            defaults: { /*...*/ },
            messages: { /*...*/ },
            aliases: { /*...*/ }
        }
    }
}

Actions 參數驗證

要執行參數驗證之前,需要先在 action 定義好需要的參數名稱,然後再建立參數的驗證綱目。

範例:

const { ServiceBroker } = require("moleculer");

const broker = new ServiceBroker({
    validator: true // 預設為啟用
});

broker.createService({
    name: "say",
    actions: {
        hello: {
            // 驗證器綱目
            params: {
                name: { type: "string", min: 2 }
            },
            handler(ctx) {
                return "Hello " + ctx.params.name;
            }
        }
    }
});

broker.call("say.hello").then(console.log)
    .catch(err => console.error(err.message));
// -> throw ValidationError: "The 'name' field is required!"

broker.call("say.hello", { name: 123 }).then(console.log)
    .catch(err => console.error(err.message));
// -> throw ValidationError: "The 'name' field must be a string!"

broker.call("say.hello", { name: "Walter" }).then(console.log)
    .catch(err => console.error(err.message));

範例:驗證綱目

{
    id: { type: "number", positive: true, integer: true },
    name: { type: "string", min: 3, max: 255 },
    status: "boolean" // 簡寫定義,更多請參閱[3]
}

異步客製化驗證器

Fastest Validator 在 v1.11.0 版後支援了異步客製化驗證器,只要在 moleculer.config.js 設定 useNewCustomCheckerFunction: true 來啟動這個功能,就可以利用它來建立客製化驗證函數。在 Moleculer 中,客製化驗證器函數可以使用 ctx 資訊,它會被放在 context.meta 中,你可以透過 ctx 來使用 contextservicebroker 資源,因此你可以在驗證器中執行異步呼叫 (例如: ctx.call )。

範例:啟用異步客製化驗證器

moleculer.config.js

module.exports = {
    validator: {
        type: "FastestValidator",
        options: {
            useNewCustomCheckerFunction: true,
            defaults: { /*...*/ },
            messages: { /*...*/ },
            aliases: { /*...*/ }
        }
    }
}

範例:使用異步客製化驗證器

posts.service.js

module.exports = {
    name: "posts",
    actions: {
        params: {
            $$async: true,
            owner: {
                type: "string",
                custom: async (value, errors, schema, name, parent, context) => {
                    // 由 meta 取得 ctx
                    const ctx = context.meta;
                    // 呼叫其它服務
                    const res = await ctx.call("users.isValid", {
                        id: value
                    });
                    if (res !== true)
                        errors.push({
                            type: "invalidOwner",
                            field: "owner",
                            actual: value
                        });
                    return value;
                }
            },
        },
        handler(ctx) {
            // ...
        }
    }
};

事件參數驗證

事件也可以使用參數驗證,它的參數驗證綱目設定方式與 Actions 類似。

注意,驗證錯誤時並不會像 Actions 一樣返回給發送來源,它只會將錯誤紀錄在 log 或是在全域錯誤處理被捕獲。

mailer.service.js

module.exports = {
    name: "mailer",
    events: {
        "send.mail": {
            params: {
                from: "string|optional",
                to: "email",
                subject: "string"
            },
            handler(ctx) {
                this.logger.info("Event received, parameters OK!", ctx.params);
            }
        }
    }
};

客製化驗證器

你也可以建立客製化的驗證器,官方建議可以參考 Fastest Validator[4] 的原始碼來修改,再實作 compilevalidate 方法。

範例:建立 Joi 驗證器

使用前請安裝 joi[5] 套件 npm install joi --save

joi.validator.js

const BaseValidator = require("moleculer").Validators.Base;
const { ValidationError } = require("moleculer").Errors;

class JoiValidator extends BaseValidator {
    constructor() {
        super();
    }

    compile(schema) {
        return (params) => this.validate(params, schema);
    }

    validate(params, schema) {
        const res = schema.validate(params);
        if (res.error)
            throw new ValidationError(res.error.message, null, res.error.details);

        return true;
    }
}

module.exports = JoiValidator;

範例:使用 Joi 驗證器

greeter.service.js

const { ServiceBroker } = require("moleculer");
const Joi = require("joi");
const JoiValidator = require("./joi.validator");

let broker = new ServiceBroker({
    logger: true,
    validator: new JoiValidator // 使用 Joi 驗證器
});

// --- 測試 BROKER ---
broker.createService({
    name: "greeter",
    actions: {
        hello: {
            /*params: {
                name: { type: "string", min: 4 }
            },*/
            params: Joi.object().keys({
                name: Joi.string().min(4).max(30).required()
            }),
            handler(ctx) {
                return `Hello ${ctx.params.name}`;
            }
        }
    }
});

broker.start()
    .then(() => broker.call("greeter.hello").then(res => broker.logger.info(res)))
    .catch(err => broker.logger.error(err.message, err.data))
    // -> "name" is required ...
    .then(() => broker.call("greeter.hello", { name: 100 }).then(res => broker.logger.info(res)))
    .catch(err => broker.logger.error(err.message, err.data))
    // -> "name" must be a string ...
    .then(() => broker.call("greeter.hello", { name: "Joe" }).then(res => broker.logger.info(res)))
    .catch(err => broker.logger.error(err.message, err.data))
    // -> "name" length must be at least 4 characters long ...
    .then(() => broker.call("greeter.hello", { name: "John" }).then(res => broker.logger.info(res)))
    .catch(err => broker.logger.error(err.message, err.data));

更多驗證器可以參考官方模組清單:

https://moleculer.services/modules.html#validation

參考文獻

[1] Parameter Validation, https://moleculer.services/docs/0.14/validating.html
[2] fastest-validator, https://github.com/icebob/fastest-validator
[3] fastest-validator shorthand-definitions, https://github.com/icebob/fastest-validator#shorthand-definitions
[4] Moleculer Fastest, https://github.com/moleculerjs/moleculer/blob/master/src/validators/fastest.js
[5] joi, https://joi.dev/

家家酒小劇場

  • Otter - 應該在那些地方設定參數驗證呢?
  • Boxy - 單體式系統可能只需要在 API 驗證參數,而微服務可能包含多個遠端服務,因此必須審慎評估每個微服務需求來做參數驗證。

上一篇
Day 18 : 快取
下一篇
Day 20 : Metrics
系列文
Moleculer 家家酒31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
json_liang
iT邦研究生 5 級 ‧ 2022-09-19 00:27:53

推 joi ,我之前用 nest js 也是用 joi 來做驗證器。

我要留言

立即登入留言